/*
 * Copyright 2001 Alexander Boverman and the original authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "WtTable.h"
#include <ctype.h>

int WtTableSize(Tcl_Obj *tbl)
{
  return WtGetTableMap(tbl)->numEntries;
}

int WtTableHas(Tcl_Obj *tbl, Tcl_Obj *key)
{
  return Tcl_FindHashEntry(WtGetTableMap(tbl), (char *)key) !=
    NULL;
}

Tcl_Obj *WtTableGet(Tcl_Obj *tbl, Tcl_Obj *key)
{
  Tcl_HashEntry *ent = Tcl_FindHashEntry(WtGetTableMap(tbl),
    (char *)key);
  return ent ? (Tcl_Obj *)Tcl_GetHashValue(ent) : NULL;
}

Tcl_Obj *WtTableGetDefault(Tcl_Obj *tbl, Tcl_Obj *key,
    Tcl_Obj *fallback)
{
  Tcl_Obj *val = WtTableGet(tbl, key);
  return val ? val : fallback;
}

int WtTableGetFirst(Tcl_Obj *tbl, Tcl_Obj *key, Tcl_Obj **val,
    Tcl_Interp *interp)
{
  int ok = 1;
  Tcl_Obj *list;

  *val = NULL;
  list = WtTableGet(tbl, key);

  if (!list) {
    ok = 1;
  } else if (Tcl_ListObjIndex(interp, list, 0, val) == TCL_OK) {
    ok = 1;
  }

  return ok;
}

int WtTableGetFirstDefault(Tcl_Obj *tbl, Tcl_Obj *key,
    Tcl_Obj **val, Tcl_Obj *fallback, Tcl_Interp *interp)
{
  int ok = WtTableGetFirst(tbl, key, val, interp);
  if (ok && !*val) {
    *val = fallback;
  }
  return ok;
}

int WtTableGetList(Tcl_Obj *tbl, Tcl_Obj *key, Tcl_Obj **list,
    Tcl_Interp *interp)
{
  int ok = 1, len;

  *list = WtTableGet(tbl, key);

  if (*list) {
    if (Tcl_ListObjLength(interp, *list, &len) != TCL_OK) {
      *list = NULL;
      ok = 0;
    }
  }

  return ok;
}

int WtTableGetListDefault(Tcl_Obj *tbl, Tcl_Obj *key,
    Tcl_Obj **list, Tcl_Obj *fallback, Tcl_Interp *interp)
{
  int ok = WtTableGetList(tbl, key, list, interp);
  if (ok && !*list) {
    *list = fallback;
  }
  return ok;
}

/* Set a table entry */

void WtTableSet(Tcl_Obj *tbl, Tcl_Obj *key, Tcl_Obj *val)
{
  WtTableSetInternal(WtTableRep(tbl), key, val);
  Tcl_InvalidateStringRep(tbl);
}

void WtTableSetInternal(WtTable *tbl, Tcl_Obj *key, Tcl_Obj *val)
{
  int ok = 0, created;
  Tcl_HashEntry *ent;

  ent = Tcl_CreateHashEntry(&tbl->items, (char *)key, &created);
  if (ent) {
    Tcl_IncrRefCount(val);
    if (!created) {
      Tcl_DecrRefCount((Tcl_Obj *)Tcl_GetHashValue(ent));
    }
    Tcl_SetHashValue(ent, val);
  }
}

void WtTableSetDefault(Tcl_Obj *tbl, Tcl_Obj *key, Tcl_Obj *val,
    Tcl_Obj **result)
{
  Tcl_Obj *old;

  *result = NULL;
  old = WtTableGet(tbl, key);

  if (old) {
    *result = old;
  } else {
    WtTableSet(tbl, key, val);
    *result = val;
  }
}

void WtTableSetOneValueList(Tcl_Obj *tbl, Tcl_Obj *key,
    Tcl_Obj *val, Tcl_Obj **result)
{
  Tcl_Obj *list = Tcl_NewListObj(1, &val);

  WtTableSet(tbl, key, list);

  if (result) {
    *result = list;
  }
}

void WtTableSetOneValueListDefault(Tcl_Obj *tbl, Tcl_Obj *key,
    Tcl_Obj *val, Tcl_Obj **result)
{
  Tcl_Obj *old = WtTableGet(tbl, key);

  if (!old) {
    WtTableSetOneValueList(tbl, key, val, &old);
  }

  if (result) {
    *result = old;
  }
}

int WtTableAppendToList(Tcl_Obj *tbl, Tcl_Obj *key,
    Tcl_Obj *val, Tcl_Obj **list, Tcl_Interp *interp)
{
  return WtTableAppendToListInternal(tbl, key, val, 0,
    list, interp);
}

int WtTableAppendListToList(Tcl_Obj *tbl, Tcl_Obj *key,
    Tcl_Obj *valList, Tcl_Obj **list, Tcl_Interp *interp)
{
  return WtTableAppendToListInternal(tbl, key, valList, 1,
    list, interp);
}

int WtTableAppendToListInternal(Tcl_Obj *tbl, Tcl_Obj *key,
    Tcl_Obj *val, int multipleVals, Tcl_Obj **list,
    Tcl_Interp *interp)
{
  int ok = 0, len;
  Tcl_Obj *old, *result = NULL;

  old = WtTableGet(tbl, key);

  if (old) {
    if (Tcl_ListObjLength(interp, old, &len) == TCL_OK) {
      result = Tcl_IsShared(old) ? Tcl_DuplicateObj(old) : old;
      if (result) {
        if (multipleVals) {
          ok = Tcl_ListObjAppendList(interp, result, val) == TCL_OK;
        } else {
          ok = Tcl_ListObjAppendElement(interp, result, val) == TCL_OK;
        }

        if (ok && result != old) {
          WtTableSet(tbl, key, result);
        }

        if (!ok) {
          if (result != old) {
            Tcl_DecrRefCount(result);
          }
          result = NULL;
        }
      }
    }
  } else {
    result = multipleVals ? val : Tcl_NewListObj(1, &val);
    WtTableSet(tbl, key, result);
    ok = 1;
  }

  if (list) {
    *list = result;
  }

  return ok;
}

int WtTableKeys(Tcl_Obj *tbl, Tcl_Obj **keys, Tcl_Interp *interp)
{
  int ok = 1;
  Tcl_HashTable *hashTable;
  Tcl_HashEntry *ent;
  Tcl_HashSearch search;
  Tcl_Obj *key;

  *keys = Tcl_NewListObj(0, NULL);
  hashTable = WtGetTableMap(tbl);
  ent = Tcl_FirstHashEntry(hashTable, &search);

  while (ent) {
    key = (Tcl_Obj *)Tcl_GetHashKey(hashTable, ent);
    if (key) {
      if (Tcl_ListObjAppendElement(interp, *keys, key) != TCL_OK) {
        ok = 0;
        break;
      }
    }
    ent = Tcl_NextHashEntry(&search);
  }

  if (!ok && *keys) {
    Tcl_DecrRefCount(*keys);
    *keys = NULL;
  }

  return ok;
}

int WtTableValues(Tcl_Obj *tbl, Tcl_Obj **vals, Tcl_Interp *interp)
{
  int ok = 1;
  Tcl_HashTable *hashTable;
  Tcl_HashEntry *ent;
  Tcl_HashSearch search;
  Tcl_Obj *val;

  *vals = Tcl_NewListObj(0, NULL);
  hashTable = WtGetTableMap(tbl);
  ent = Tcl_FirstHashEntry(hashTable, &search);

  while (ent) {
    val = (Tcl_Obj *)Tcl_GetHashValue(ent);
    if (val) {
      if (Tcl_ListObjAppendElement(interp, *vals, val) != TCL_OK) {
        ok = 0;
        break;
      }
    }
    ent = Tcl_NextHashEntry(&search);
  }

  if (!ok && *vals) {
    Tcl_DecrRefCount(*vals);
    *vals = NULL;
  }

  return ok;
}

int WtTableFirstValues(Tcl_Obj *tbl, Tcl_Obj **vals,
    Tcl_Interp *interp)
{
  int ok = 1;
  Tcl_HashTable *hashTable;
  Tcl_HashEntry *ent;
  Tcl_HashSearch search;
  Tcl_Obj *val, *first;

  *vals = Tcl_NewListObj(0, NULL);
  hashTable = WtGetTableMap(tbl);
  ent = Tcl_FirstHashEntry(hashTable, &search);

  while (ent) {
    val = (Tcl_Obj *)Tcl_GetHashValue(ent);
    if (val) {
      if (Tcl_ListObjIndex(interp, val, 0, &first) != TCL_OK) {
        ok = 0;
        break;
      }
      if (Tcl_ListObjAppendElement(interp, *vals, first) != TCL_OK) {
        ok = 0;
        break;
      }
    }
    ent = Tcl_NextHashEntry(&search);
  }

  if (!ok && *vals) {
    Tcl_DecrRefCount(*vals);
    *vals = NULL;
  }

  return ok;
}

void WtTableRemove(Tcl_Obj *tbl, Tcl_Obj *key)
{
  Tcl_Obj *val;
  Tcl_HashEntry *ent;

  ent = Tcl_FindHashEntry(WtGetTableMap(tbl), (char *)key);

  if (ent) {
    val = Tcl_GetHashValue(ent);
    if (val) {
      Tcl_DecrRefCount(val);
    }    
    Tcl_DeleteHashEntry(ent);
    Tcl_InvalidateStringRep(tbl);
  }
}

int WtTableRemoveList(Tcl_Obj *tbl, Tcl_Obj *list,
    Tcl_Interp *interp)
{
  int ok = 0, len, i;
  Tcl_Obj *key;

  if (Tcl_ListObjLength(interp, list, &len) == TCL_OK) {
    ok = 1;
    for (i = 0; i < len; ++i) {
      if (Tcl_ListObjIndex(NULL, list, i, &key) != TCL_OK) {
        ok = 0;
        break;
      }
      WtTableRemove(tbl, key);
    }
  }

  return ok;
}

int WtUpdateTable(Tcl_Obj *dest, Tcl_Obj *src)
{
  int ok = 1;
  Tcl_HashTable *hashTable;
  Tcl_HashEntry *ent;
  Tcl_HashSearch search;
  Tcl_Obj *key, *val;

  hashTable = WtGetTableMap(src);
  ent = Tcl_FirstHashEntry(hashTable, &search);
  while (ent) {
    key = (Tcl_Obj *)Tcl_GetHashKey(hashTable, ent);
    val = (Tcl_Obj *)Tcl_GetHashValue(ent);
    WtTableSet(dest, key, val);
    ent = Tcl_NextHashEntry(&search);
  }

  return ok;
}

void WtClearTable(Tcl_Obj *tbl)
{
  WtDeleteTableItems(WtTableRep(tbl));
  WtInitTableItems(WtTableRep(tbl), tbl->typePtr);
  Tcl_InvalidateStringRep(tbl);
}

/* Convert the object to a table if necessary */

int WtConvertToTableObj(Tcl_Obj *obj, Tcl_ObjType *type, Tcl_Interp *interp)
{
  int ok = 1, objc, i;
  Tcl_Obj **objv, *tblObj = NULL;
  WtTable *tbl;

  if (type != &WtCaselessTableType) {
    type = &WtTableType;
  }

  if (obj->typePtr != type) {
    /* XXX: Use unserialization code from Tcl list obj instead */

    if (Tcl_ListObjGetElements(interp, obj, &objc, &objv) != TCL_OK) {
      ok = 0;
    } else if (objc & 1) {
      if (interp) {
        Tcl_AppendResult(interp, "Cannot create table from list "
          "with odd number of elements.", NULL);
      }
      ok = 0;
    }

    if (ok) {
      if (tbl = (WtTable *)ckalloc(sizeof(WtTable))) {
        WtInitTableItems(tbl, type);
        for (i = 0; i < objc; i += 2) {
          WtTableSetInternal(tbl, objv[i], objv[i + 1]);
        }
        if (!ok) {
          WtDeleteTableItems(tbl);
          ckfree((char *)tbl);
        } else {
          if (obj->typePtr && obj->typePtr->freeIntRepProc) {
            obj->typePtr->freeIntRepProc(obj);
          }
          obj->internalRep.twoPtrValue.ptr1 = tbl;
          obj->internalRep.twoPtrValue.ptr2 = NULL;
          obj->typePtr = type;
          Tcl_InvalidateStringRep(obj);
        }
      }
    }
  }

  return ok;
}

/* Create the entry map */

void WtInitTableItems(WtTable *tbl, Tcl_ObjType *type)
{
  if (type == &WtCaselessTableType) {
    Tcl_InitCustomHashTable(&tbl->items,
      TCL_CUSTOM_PTR_KEYS, &WtCaselessHashKeyType);
  } else {
    Tcl_InitObjHashTable(&tbl->items);
  }
}

void WtDeleteTableItems(WtTable *tbl)
{
  Tcl_HashTable *hashTable;
  Tcl_HashEntry *ent;
  Tcl_HashSearch search;
  Tcl_Obj *val;

  hashTable = &tbl->items;
  ent = Tcl_FirstHashEntry(hashTable, &search);

  while (ent) {
    val = (Tcl_Obj *)Tcl_GetHashValue(ent);
    if (val) {
      Tcl_DecrRefCount(val);
    }
    ent = Tcl_NextHashEntry(&search);
  }

  Tcl_DeleteHashTable(hashTable);
}

WtTable *WtTableRep(Tcl_Obj *tbl)
{
  return (WtTable *)(tbl->internalRep.twoPtrValue.ptr1);
}

Tcl_HashTable *WtGetTableMap(Tcl_Obj *tbl)
{
  return &(WtTableRep(tbl)->items);
}

int WtIsTableType(Tcl_ObjType *type)
{
  return type == &WtTableType || type == &WtCaselessTableType;
}

/* Standard table object */

Tcl_Obj *WtNewTableObj()
{
  return WtNewTableObjWithType(&WtTableType);
}

struct Tcl_ObjType WtTableType =
{
  "table",
  WtFreeTable,
  WtDupTable,
  WtUpdateTableString,
  WtSetTableFromAny
};

void WtFreeTable(Tcl_Obj *tbl)
{
  WtFreeTableObj(tbl);
}

void WtDupTable(Tcl_Obj *src, Tcl_Obj *dest)
{
  WtDupTableObj(src, dest);
}

void WtUpdateTableString(Tcl_Obj *tbl)
{
  WtUpdateTableObjString(tbl);
}

int WtSetTableFromAny(Tcl_Interp *interp, Tcl_Obj *obj)
{
  return WtSetTableObjFromAny(interp, obj, &WtTableType);
}

int WtConvertToTable(Tcl_Obj *obj, Tcl_Interp *interp)
{
  return WtConvertToTableObj(obj, &WtTableType, interp);
}

/* Case-insensitive table object */

Tcl_Obj *WtNewCaselessTableObj()
{
  return WtNewTableObjWithType(&WtCaselessTableType);
}

struct Tcl_ObjType WtCaselessTableType =
{
  "itable",
  WtFreeCaselessTable,
  WtDupCaselessTable,
  WtUpdateCaselessTableString,
  WtSetCaselessTableFromAny
};

void WtFreeCaselessTable(Tcl_Obj *tbl)
{
  WtFreeTableObj(tbl);
}

void WtDupCaselessTable(Tcl_Obj *src, Tcl_Obj *dest)
{
  WtDupTableObj(src, dest);
}

void WtUpdateCaselessTableString(Tcl_Obj *tbl)
{
  WtUpdateTableObjString(tbl);
}

int WtSetCaselessTableFromAny(Tcl_Interp *interp, Tcl_Obj *obj)
{
  return WtSetTableObjFromAny(interp, obj, &WtCaselessTableType);
}

int WtConvertToCaselessTable(Tcl_Obj *obj, Tcl_Interp *interp)
{
  return WtConvertToTableObj(obj, &WtCaselessTableType, interp);
}

/* Custom hash table for case-insensitive tables */

Tcl_HashKeyType WtCaselessHashKeyType = {
  TCL_HASH_KEY_TYPE_VERSION,          /* version */
  0,                                  /* flags */
  HashCaselessKey,                    /* hashKeyProc */
  CompareCaselessKeys,                /* compareKeysProc */
  AllocCaselessEntry ,                /* allocEntryProc */
  FreeCaselessEntry                   /* freeEntryProc */
};

/* Caseless hashKeyProc */

unsigned int HashCaselessKey(Tcl_HashTable *hashTable, void *keyPtr)
{
  Tcl_Obj *key = (Tcl_Obj *)keyPtr;
  const char *string;
  int length, i;
  unsigned int result = 0;

  string = Tcl_GetString(key);
  length = key->length;

  for (i = 0; i < length; i++) {
    result += (result << 3) + tolower(string[i]);
  }

  return result;  
}

/* Caseless compareKeysProc */

int CompareCaselessKeys(void *keyPtr, Tcl_HashEntry *entry)
{
  Tcl_Obj *obj1 = (Tcl_Obj *)keyPtr;
  Tcl_Obj *obj2 = (Tcl_Obj *)(entry->key.oneWordValue);
  const char *p1, *p2;
  int l1, l2;

  if (obj1 == obj2) {
      return 1;
  }

  p1 = Tcl_GetString(obj1);
  l1 = obj1->length;

  p2 = Tcl_GetString(obj2);
  l2 = obj2->length;

  if (l1 == l2) {
    while (l1--) {
      if (tolower(*p1) != tolower(*p2)) {
        return 0;
      }
      p1++;
      p2++;
    }
    return 1;
  }

  return 0;
}

/* Caseless allocEntryProc */

Tcl_HashEntry *AllocCaselessEntry(Tcl_HashTable *hashTable, void *keyPtr)
{
  Tcl_Obj *key = (Tcl_Obj *)keyPtr;
  Tcl_HashEntry *ent;
  ent = (Tcl_HashEntry *)ckalloc((unsigned)(sizeof(Tcl_HashEntry)));
  ent->key.oneWordValue = (char *)key;
  Tcl_IncrRefCount(key);
  return ent;
}

/* Caseless freeEntryProc */

void FreeCaselessEntry(Tcl_HashEntry *ent)
{
  Tcl_Obj *key = (Tcl_Obj *)ent->key.oneWordValue;
  Tcl_DecrRefCount(key);
  ckfree((char *)ent);
}

/* Type-guarded table functions */

Tcl_Obj *WtNewTableObjWithType(Tcl_ObjType *type)
{
  WtTable *tbl;
  Tcl_Obj *obj = NULL;

  tbl = (WtTable *)ckalloc(sizeof(WtTable));
  if (tbl) {
    obj = Tcl_NewObj();
    obj->internalRep.twoPtrValue.ptr1 = tbl;
    obj->internalRep.twoPtrValue.ptr2 = NULL;
    obj->typePtr = type ? type : &WtTableType;
    WtInitTableItems(tbl, type);
    Tcl_InvalidateStringRep(obj);
  }

  return obj;
}

Tcl_Obj *WtNewTableObjWithElements(Tcl_ObjType *type,
  int objc, const Tcl_Obj *objv[], Tcl_Interp *interp)
{
  Tcl_Obj *tbl = NULL;
  int i;

  if (objc & 1) {
    if (interp) {
      Tcl_AppendResult(interp, "Cannot create table from odd "
        "number of elements.", NULL);
    }
  } else {
    tbl = WtNewTableObjWithType(type);
    for (i = 0; i < objc; i += 2) {
      WtTableSet(tbl, (Tcl_Obj *)objv[i], (Tcl_Obj *)objv[i + 1]);
    }
  }

  return tbl;
}

int WtTableObjSize(Tcl_Obj *tbl, int *size, Tcl_ObjType *type,
    Tcl_Interp *interp)
{
  int ok = 0;

  if (WtConvertToTableObj(tbl, type, interp)) {
    *size = WtTableSize(tbl);
    ok = 1;
  }

  return ok;
}

int WtTableObjHas(Tcl_Obj *tbl, Tcl_Obj *key, int *has,
    Tcl_ObjType *type, Tcl_Interp *interp)
{
  int ok = 0;

  if (WtConvertToTableObj(tbl, type, interp)) {
    *has = WtTableHas(tbl, key);
    ok = 1;
  } else {
    *has = -1;
  }

  return ok;
}

int WtTableObjGet(Tcl_Obj *tbl, Tcl_Obj *key, Tcl_Obj **val,
    Tcl_ObjType *type, Tcl_Interp *interp)
{
  int ok = 0;

  if (WtConvertToTableObj(tbl, type, interp)) {
    *val = WtTableGet(tbl, key);
    ok = 1;
  } else {
    *val = NULL;
  }

  return ok;
}

int WtTableObjGetDefault(Tcl_Obj *tbl, Tcl_Obj *key, Tcl_Obj **val,
    Tcl_Obj *fallback, Tcl_ObjType *type, Tcl_Interp *interp)
{
  int ok = 0;

  if (WtConvertToTableObj(tbl, type, interp)) {
    *val = WtTableGetDefault(tbl, key, fallback);
    ok = 1;
  } else {
    *val = NULL;
  }

  return ok;
}

int WtTableObjGetFirst(Tcl_Obj *tbl, Tcl_Obj *key, Tcl_Obj **val,
    Tcl_ObjType *type, Tcl_Interp *interp)
{
  int ok = 0;

  if (WtConvertToTableObj(tbl, type, interp)) {
    ok = WtTableGetFirst(tbl, key, val, interp);
  } else {
    *val = NULL;
  }

  return ok;
}

int WtTableObjGetFirstDefault(Tcl_Obj *tbl, Tcl_Obj *key,
    Tcl_Obj **val, Tcl_Obj *fallback, Tcl_ObjType *type,
    Tcl_Interp *interp)
{
  int ok = 0;

  if (WtConvertToTableObj(tbl, type, interp)) {
    ok = WtTableGetFirstDefault(tbl, key, val, fallback,
      interp);
  } else {
    *val = NULL;
  }

  return ok;
}

int WtTableObjGetList(Tcl_Obj *tbl, Tcl_Obj *key, Tcl_Obj **list,
    Tcl_ObjType *type, Tcl_Interp *interp)
{
  int ok = 0;

  if (WtConvertToTableObj(tbl, type, interp)) {
    ok = WtTableGetList(tbl, key, list, interp);
  } else {
    *list = NULL;
  }

  return ok;
}

int WtTableObjGetListDefault(Tcl_Obj *tbl, Tcl_Obj *key,
    Tcl_Obj **list, Tcl_Obj *fallback, Tcl_ObjType *type,
    Tcl_Interp *interp)
{
  int ok = 0;

  if (WtConvertToTableObj(tbl, type, interp)) {
    ok = WtTableGetListDefault(tbl, key, list, fallback, interp);
  } else {
    *list = NULL;
  }

  return ok;
}

/* Set a table entry */

int WtTableObjSet(Tcl_Obj *tbl, Tcl_Obj *key, Tcl_Obj *val,
    Tcl_ObjType *type, Tcl_Interp *interp)
{
  int ok = 0;

  if (WtConvertToTableObj(tbl, type, interp)) {
    WtTableSet(tbl, key, val);
    ok = 1;
  }

  return ok;
}

/* Set a table entry if it doesn't exist */

int WtTableObjSetDefault(Tcl_Obj *tbl, Tcl_Obj *key, Tcl_Obj *val,
    Tcl_Obj **result, Tcl_ObjType *type, Tcl_Interp *interp)
{
  int ok = 0;

  if (WtConvertToTableObj(tbl, type, interp)) {
    WtTableSetDefault(tbl, key, val, result);
    ok = 1;
  } else {
    *result = NULL;
  }

  return ok;
}

/* Insert a list with one element */

int WtTableObjSetOneValueList(Tcl_Obj *tbl, Tcl_Obj *key,
    Tcl_Obj *val, Tcl_Obj **result, Tcl_ObjType *type,
    Tcl_Interp *interp)
{
  int ok = 0;

  if (result) {
    *result = NULL;
  }

  if (WtConvertToTableObj(tbl, type, interp)) {
    WtTableSetOneValueList(tbl, key, val, result);
    ok = 1;
  }

  return ok;
}

/* Insert a list with one element if it doesn't exist */

int WtTableObjSetOneValueListDefault(Tcl_Obj *tbl, Tcl_Obj *key,
    Tcl_Obj *val, Tcl_Obj **result, Tcl_ObjType *type,
    Tcl_Interp *interp)
{
  int ok = 0;

  if (result) {
    *result = NULL;
  }

  if (WtConvertToTableObj(tbl, type, interp)) {
    WtTableSetOneValueListDefault(tbl, key, val, result);
    ok = 1;
  }

  return ok;
}

/* Append an element to a list */

int WtTableObjAppendToList(Tcl_Obj *tbl, Tcl_Obj *key,
    Tcl_Obj *val, Tcl_Obj **list, Tcl_ObjType *type,
    Tcl_Interp *interp)
{
  int ok = 0;

  if (WtConvertToTableObj(tbl, type, interp)) {
    ok = WtTableAppendToList(tbl, key, val, list, interp);
  }

  return ok;
}

/* Append several elements to a list */

int WtTableObjAppendListToList(Tcl_Obj *tbl, Tcl_Obj *key,
    Tcl_Obj *valList, Tcl_Obj **list, Tcl_ObjType *type,
    Tcl_Interp *interp)
{
  int ok = 0;

  if (WtConvertToTableObj(tbl, type, interp)) {
    ok = WtTableAppendListToList(tbl, key, valList, list, interp);
  } else {
    *list = NULL;
  }

  return ok;
}

/* Get keys */

int WtTableObjKeys(Tcl_Obj *tbl, Tcl_Obj **keys, Tcl_ObjType *type,
    Tcl_Interp *interp)
{
  int ok = 0;

  if (WtConvertToTableObj(tbl, type, interp)) {
    ok = WtTableKeys(tbl, keys, interp);
  } else {
    *keys = NULL;
  }

  return ok;
}

/* Get values */

int WtTableObjValues(Tcl_Obj *tbl, Tcl_Obj **vals,
  Tcl_ObjType *type, Tcl_Interp *interp)
{
  int ok = 0;

  if (WtConvertToTableObj(tbl, type, interp)) {
    ok = WtTableValues(tbl, vals, interp);
  } else {
    *vals = NULL;
  }

  return ok;
}

/* Get first values */

int WtTableObjFirstValues(Tcl_Obj *tbl, Tcl_Obj **vals,
    Tcl_ObjType *type, Tcl_Interp *interp)
{
  int ok = 0;

  if (WtConvertToTableObj(tbl, type, interp)) {
    ok = WtTableFirstValues(tbl, vals, interp);
  } else {
    *vals = NULL;
  }

  return ok;
}

/* Remove an entry */

int WtTableObjRemove(Tcl_Obj *tbl, Tcl_Obj *key,
    Tcl_ObjType *type, Tcl_Interp *interp)
{
  int ok = 0;

  if (WtConvertToTableObj(tbl, type, interp)) {
    WtTableRemove(tbl, key);
    ok = 1;
  }

  return ok;
}

/* Remove a list of entries */

int WtTableObjRemoveList(Tcl_Obj *tbl, Tcl_Obj *list,
    Tcl_ObjType *type, Tcl_Interp *interp)
{
  int ok = 0;

  if (WtConvertToTableObj(tbl, type, interp)) {
    ok = WtTableRemoveList(tbl, list, interp);
  }

  return ok;
}

/* Clear the contents of a table */

int WtClearTableObj(Tcl_Obj *tbl, Tcl_ObjType *type, Tcl_Interp *interp)
{
  int ok = 0;

  if (WtConvertToTableObj(tbl, type, interp)) {
    WtClearTable(tbl);
    ok = 1;
  }

  return ok;
}

/* Type handlers */

void WtFreeTableObj(Tcl_Obj *tbl)
{
  WtDeleteTableItems(WtTableRep(tbl));
  ckfree((char *)WtTableRep(tbl));
}

void WtDupTableObj(Tcl_Obj *src, Tcl_Obj *dest)
{
  WtTable *srcTbl = WtTableRep(src), *destTbl;
  Tcl_HashTable *srcMap = WtGetTableMap(src), *destMap;
  Tcl_HashEntry *srcEnt, *destEnt;
  Tcl_HashSearch search;
  Tcl_Obj *val;
  int created;

  destTbl = (WtTable *)ckalloc(sizeof(WtTable));
  dest->internalRep.twoPtrValue.ptr1 = destTbl;
  dest->internalRep.twoPtrValue.ptr2 = NULL;
  dest->typePtr = src->typePtr;
  WtInitTableItems(destTbl, dest->typePtr);
  destMap = WtGetTableMap(dest);

  srcEnt = Tcl_FirstHashEntry(srcMap, &search);

  while (srcEnt) {
    destEnt = Tcl_CreateHashEntry(destMap,
      Tcl_GetHashKey(srcMap, srcEnt), &created);
    val = (Tcl_Obj *)Tcl_GetHashValue(srcEnt);
    Tcl_SetHashValue(destEnt, val);
    Tcl_IncrRefCount(val);
    srcEnt = Tcl_NextHashEntry(&search);
  }

  Tcl_InvalidateStringRep(dest);
}

void WtUpdateTableObjString(Tcl_Obj *tbl)
{
  int ok = 1, n;
  Tcl_HashTable *hashTable;
  Tcl_HashEntry *ent;
  Tcl_HashSearch search;
  Tcl_Obj *list, *key, *val;
  const char *bytes;

  list = Tcl_NewListObj(0, NULL);
  hashTable = WtGetTableMap(tbl);
  ent = Tcl_FirstHashEntry(hashTable, &search);

  while (ent) {
    key = (Tcl_Obj *)Tcl_GetHashKey(hashTable, ent);
    val = (Tcl_Obj *)Tcl_GetHashValue(ent);
    if (Tcl_ListObjAppendElement(NULL, list, key) != TCL_OK) {
      ok = 0;
      break;
    }
    if (Tcl_ListObjAppendElement(NULL, list, val) != TCL_OK) {
      ok = 0;
      break;
    }
    ent = Tcl_NextHashEntry(&search);
  }

  if (ok) {
    bytes = Tcl_GetStringFromObj(list, &n);
    if (!bytes) {
      ok = 0;
    } else {
      tbl->bytes = Tcl_Alloc(n + 1);
      memcpy(tbl->bytes, bytes, n + 1);
      tbl->length = n;
    }
  }

  if (list) {
    Tcl_DecrRefCount(list);
  }

  if (!ok) {
    tbl->bytes = NULL;
    tbl->length = 0;
  }
}

int WtSetTableObjFromAny(Tcl_Interp *interp, Tcl_Obj *obj,
    Tcl_ObjType *type)
{
  return WtConvertToTableObj(obj, type, interp) ?
    TCL_OK : TCL_ERROR;
}
